/*
 * Copyright Konstantin Triger <kostat@gmail.com> 
 * 
 * This file is part of Jaque 2 JPA - JAva QUEry 2 JPA library <http://code.google.com/p/jaque/>.
 * 
 * Jaque is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Jaque is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package jaque.jpa;

import java.util.*;
import jaque.expressions.*;
import static jaque.Query.*;
import static jaque.Operation.*;

abstract class BaseDbExpressionVisitor extends SimpleExpressionVisitor
		implements DbExpressionVisitor<Expression> {

	// @Override
	// public Expression visit(AliasExpression e) {
	// return e;
	// }

	// @Override
	public Expression visit(AggregateExpression e) {
		Expression arg = e.getArgument();
		if (arg != null) {
			arg = arg.apply(this);
			if (arg != e.getArgument()) {
				return new AggregateExpression(e.getResultType(), e
						.getAggregateType(), arg, e.isDistinct());
			}
		}
		return e;
	}

	// @Override
	public Expression visit(AggregateSubqueryExpression ase) {
		Expression e = ase.getAggregateAsSubquery();
		assert (e instanceof ScalarExpression);
		ScalarExpression subquery = (ScalarExpression) e;
		if (subquery != ase.getAggregateAsSubquery()) {
			return new AggregateSubqueryExpression(ase.getGroupByAlias(), ase
					.getAggregateInGroupSelect(), subquery);
		}
		return ase;
	}

	// @Override
	public Expression visit(BetweenExpression e) {
		Expression expr = e.getExpression();
		Expression lower = e.getLower();
		Expression upper = e.getUpper();
		if (expr != e.getExpression() || lower != e.getLower()
				|| upper != e.getUpper()) {
			return new BetweenExpression(expr, lower, upper);
		}
		return e;
	}

	//
	// @Override
	// public Expression visit(ColumnExpression e) {
	// return e;
	// }

	// @Override
	public Expression visit(ExistsExpression e) {
		SelectExpression select = (SelectExpression) e.getSelect();
		if (select != e.getSelect()) {
			return new ExistsExpression(select);
		}
		return e;
	}

	// @Override
	public Expression visit(InExpression e) {
		Expression expr = e.getExpression();
		if (e.getSelect() != null) {
			SelectExpression select = (SelectExpression) e.getSelect().apply(
					this);
			if (expr != e.getExpression() || select != e.getSelect()) {
				return new InExpression(expr, select);
			}
		} else {
			List<Expression> values = visitExpressionList(e.getValues());
			if (expr != e.getExpression() || values != e.getValues()) {
				return new InExpression(expr, values);
			}
		}
		return e;
	}

	// @Override
	// public Expression visit(IsNullExpression e) {
	// Expression expr = e.getExpression();
	// if (expr != e.getExpression()) {
	// return new IsNullExpression(expr);
	// }
	// return e;
	// }

	// @Override
	public Expression visit(JoinExpression e) {
		Expression left = visitSource(e.getLeft());
		Expression right = visitSource(e.getRight());
		Expression condition = e.getCondition().apply(this);
		if (left != e.getLeft() || right != e.getRight()
				|| condition != e.getCondition()) {
			return new JoinExpression(e.getResultType(), e.getJoin(), left,
					right, condition);
		}
		return e;
	}

	// @Override
	public Expression visit(NamedValueExpression e) {
		return e;
	}

	//
	// @Override
	// public Expression visit(ProjectionExpression e) {
	// SelectExpression source = (SelectExpression)e.getSource();
	// Expression projector = e.getProjector();
	// if (source != e.getSource() || projector != e.getProjector()) {
	// return new ProjectionExpression(source, projector, e.getAggregator());
	// }
	// return e;
	// }

	// @Override
	public Expression visit(RowNumberExpression e) {
		List<OrderExpression> orderby = visitOrderBy(e.getOrderBy());
		if (orderby != e.getOrderBy()) {
			return new RowNumberExpression(orderby);
		}
		return e;
	}

	// @Override
	public Expression visit(ScalarExpression e) {
		SelectExpression select = (SelectExpression) e.getSelect();
		if (select != e.getSelect()) {
			return new ScalarExpression(e.getResultType(), select);
		}
		return e;
	}

	// @Override
	public Expression visit(SelectExpression e) {
		Expression from = visitSource(e.getFrom());
		Expression selector = e.getSelector();
		if (selector != null)
			selector = selector.apply(this);
		Expression where = e.getWhere();
		if (where != null)
			where = where.apply(this);
		List<OrderExpression> orderBy = visitOrderBy(e.getOrderBy());
		List<Expression> groupBy = visitExpressionList(e.getGroupBy());
		// Expression skip = e.getSkip().apply(this);
		// Expression take = e.getTake().apply(this);
		// List<ColumnDeclaration> columns = this.visitColumnDeclarations(e
		// .getColumns());
		if (from != e.getFrom() || where != e.getWhere()
				|| orderBy != e.getOrderBy() || groupBy != e.getGroupBy()
				// || take != e.getTake() || skip != e.getSkip()
				// || columns != e.getColumns()
				|| selector != e.getSelector()) {
			return new SelectExpression(from, // e.getAlias(),
					selector,/* columns, from, */
					where, orderBy, groupBy, e.isDistinct()/*
															 * , skip, take
															 */);
		}
		return e;
	}

	// @Override
	// public Expression visit(TableExpression e) {
	// return e;
	// }

	// protected Expression visitSubquery(SubqueryExpression subquery) {
	// switch (subquery.getExpressionType()) {
	// case DbExpressionType.Scalar:
	// return ((ScalarExpression)subquery).apply(this);
	// case DbExpressionType.Exists:
	// return this.visitExists((ExistsExpression)subquery);
	// case DbExpressionType.In:
	// return this.visitIn((InExpression)subquery);
	// }
	// return subquery;
	// }

	// protected Expression visitScalar(ScalarExpression scalar) {
	// SelectExpression select = (SelectExpression)scalar.getSelect();
	// if (select != scalar.getSelect()) {
	// return new ScalarExpression(scalar.getResultType(), select);
	// }
	// return scalar;
	// }

	// protected List<Expression> visitExpressionList(List<Expression> original)
	// {
	// if (original != null) {
	// List<Expression> list = null;
	// for (int i = 0, n = original.size(); i < n; i++) {
	// Expression p = original.get(i).apply(this);
	// if (list != null) {
	// list.add(p);
	// } else if (p != original.get(i)) {
	// list = new ArrayList<Expression>(n);
	// for (int j = 0; j < i; j++) {
	// list.add(original.get(i));
	// }
	// list.add(p);
	// }
	// }
	// if (list != null) {
	// return Collections.unmodifiableList(list);
	// }
	// }
	// return original;
	// }

	protected Expression visitSource(Expression source) {
		return source.apply(this);
	}

	//
	// protected List<ColumnDeclaration> visitColumnDeclarations(
	// List<ColumnDeclaration> columns) {
	// List<ColumnDeclaration> alternate = null;
	// for (int i = 0, n = columns.size(); i < n; i++) {
	// ColumnDeclaration column = columns.get(i);
	// Expression e = column.getExpression().apply(this);
	// if (alternate == null && e != column.getExpression()) {
	// alternate = toList(take(columns, i));
	// }
	// if (alternate != null) {
	// alternate.add(new ColumnDeclaration(column.getName(), e));
	// }
	// }
	// if (alternate != null) {
	// return Collections.unmodifiableList(alternate);
	// }
	// return columns;
	// }

	protected List<OrderExpression> visitOrderBy(
			List<OrderExpression> expressions) {
		if (expressions != null) {
			List<OrderExpression> alternate = null;
			for (int i = 0, n = expressions.size(); i < n; i++) {
				OrderExpression expr = expressions.get(i);
				Expression e = expr.getExpression();

				if (alternate == null && e != expr.getExpression()) {
					alternate = toList(take(i, expressions)); // expressions.Take(i).ToList();
				}
				if (alternate != null) {
					alternate.add(new OrderExpression(expr.isAscending(), e));
				}
			}
			if (alternate != null) {
				return Collections.unmodifiableList(alternate);
			}
		}
		return expressions;
	}
}
